package com.wyj.springmvc.framework.servlet;

import com.alibaba.fastjson.JSON;
import com.wyj.springmvc.framework.Handler;
import com.wyj.springmvc.framework.annotation.*;
import com.wyj.springmvc.framework.pojo.SecurityProxy;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * TODO: 前端控制器
 *
 * @author wyj
 * @date 2021/11/23 22:02
 **/

public class MyDispatcherServlet extends HttpServlet  {
    /**
     * 参数名称
     */
    private static final String scanPackage = "scanPackage";
    /**
     * 配置文件
     */
    private final Properties properties = new Properties();

    /**
     * 缓存所有的class全限定类名
     */
    private final List<String> classNames = new ArrayList<>();
    /**
     * ioc容器。
     */
    private final Map<String, Object> iocMap = new HashMap<>();
    /**
     * 处理器映射器集合
     */
    private final List<Handler> handlerMappings = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //   1、 加载配置文件--获取contextConfigLocation参数值
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);
        //   2、 扫描注解类
        doScan(properties.getProperty(scanPackage));
        //   3、 初始化bean，实现ioc容器
        doInstance();
        //   4、 依赖注入
        doAutoWired();
        //   5、 构造处理器映射器handlerMappings
        initHandlerMappings();
        //   6、 处理请求···
    }

    private void initHandlerMappings() {
        if (iocMap.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, Object>> entries = iocMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            Class<?> aClass = entry.getValue().getClass();
            //首先是controller
            if (aClass.isAnnotationPresent(Controller.class)) {
                //controller被requestMapping注解修饰
                if (aClass.isAnnotationPresent(RequestMapping.class)) {
                    Handler handler = new Handler();
                    String baseUrl = aClass.getAnnotation(RequestMapping.class).value();
                    Method[] methods = aClass.getMethods();
                    for (Method method : methods) {
                        //controller的方法被requestMapping注解修饰
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            //封装handler对象
                            handler.setController(entry.getValue());
                            handler.setMethod(method);
                            String url = baseUrl + method.getAnnotation(RequestMapping.class).value();
                            handler.setPattern(Pattern.compile(url));

                            Parameter[] parameters = method.getParameters();
                            HashMap<String, Integer> parameteIndexMapping = new HashMap<>(parameters.length);
                            //将方法的参数按照顺序，保存到handler中
                            for (int i = 0; i < parameters.length; i++) {
                                if (parameters[i].getType() == HttpServletRequest.class || parameters[i].getType() == HttpServletResponse.class) {
                                    parameteIndexMapping.put(parameters[i].getType().getSimpleName(), i);
                                } else {
                                    parameteIndexMapping.put(parameters[i].getName(), i);
                                }
                            }
                            handler.setParameterIndexMapping(parameteIndexMapping);
                            handlerMappings.add(handler);
                        }
                    }
                }
            }
        }
    }

    private void doAutoWired() {
        if (iocMap.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, Object>> entries = iocMap.entrySet();
        //循环遍历ioc所有的bean，根据bean属性上是否有@autowired注解来决定是否注入对象。
        for (Map.Entry<String, Object> entry : entries) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            //遍历bean的所有属性
            for (Field declaredField : declaredFields) {
                if (!declaredField.isAnnotationPresent(Autowired.class)) {
                    continue;
                }
                //属性使用了@autowired注解修饰，给该属性赋值
                try {
                    declaredField.setAccessible(true);
                    //属性赋值
                    declaredField.set(entry.getValue(), iocMap.get(lowerFirstLetter(declaredField.getType().getSimpleName())));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doInstance() {
        //    基于路径+反射，完成对象的创建
        for (String classPath : classNames) {
            Class<?> aClass = null;
            try {
                aClass = Class.forName(classPath);
                Object bean = null;
                String beanName = null;
                // 是controller
                if (aClass.isAnnotationPresent(Controller.class)) {
                    beanName = lowerFirstLetter(aClass.getSimpleName());
                    bean = aClass.getConstructor().newInstance();
                } else if (aClass.isAnnotationPresent(Service.class)) {
                    // 是service
                    bean = aClass.getConstructor().newInstance();
                    Service annotation = aClass.getAnnotation(Service.class);
                    String serviceValue = annotation.value();
                    if (!"".equals(serviceValue)) {
                        beanName = serviceValue;
                    } else {
                        beanName = lowerFirstLetter(aClass.getSimpleName());
                    }
                    //    为方便面向接口开发，需要将service接口名称注入到ioc容器内
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        iocMap.put(lowerFirstLetter(anInterface.getSimpleName()), bean);
                    }
                }
                if (null != beanName) {
                    iocMap.put(beanName, bean);
                }

            } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
                e.printStackTrace();
            }

        }
    }


    private void doScan(String scanPackage) {
        //获取磁盘上的文件路径，分隔符为/
        String scanPackagePath = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath() + scanPackage.replaceAll("\\.", "/");
        File[] files = new File(scanPackagePath).listFiles();
        assert files != null;
        for (File file : files) {
            //子文件夹
            if (file.isDirectory()) {
                String childPackagePath = scanPackage + File.separator + file.getName();
                doScan(childPackagePath);
            } else if (file.isFile() && file.getName().endsWith(".class")) {
                //需要被加载的类文件
                classNames.add((scanPackage + File.separator).replaceAll("\\\\", ".") + file.getName().replaceAll(".class", ""));
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html; charset=utf-8");

        PrintWriter writer = resp.getWriter();
        //url是完整的请求地址：ip+端口+请求路径
        //uri是请求路径
        Handler handler = getHandler(req.getRequestURI());
        String username = req.getParameter("username");

        if (null == handler) {
            resp.getWriter().write("404 not found```");
            return;
        }

        //获取该请求的参数
        Map<String, String[]> requestParameterMap = req.getParameterMap();
        Set<Map.Entry<String, String[]>> requestParameterEntries = requestParameterMap.entrySet();

        //该handler需要的参数下标集合
        Map<String, Integer> parameterIndexMappingMap = handler.getParameterIndexMapping();
        //创建一个参数集合,作为反射调用方法的参数
        Object[] parameterArr = new Object[parameterIndexMappingMap.size()];

        //遍历参数集合,将其与需要传入的参数集合进行一一映射,参数名称和顺序要正确. httpServletRequest和HttpServletResponse排除掉
        for (Map.Entry<String, String[]> entry : requestParameterEntries) {
            //将数组类型参数,做一个转换  name=1&name=2
            String join = StringUtils.join(entry.getValue(), ",");

            if (parameterIndexMappingMap.containsKey(entry.getKey())) {
                //将传入的参数和需要反射调用的参数集合进行映射.
                parameterArr[parameterIndexMappingMap.get(entry.getKey())] = join;
            }
        }
        //将该请求的request和response也映射到反射调用的参数集合中
        parameterArr[parameterIndexMappingMap.get(HttpServletRequest.class.getSimpleName())] = req;
        parameterArr[parameterIndexMappingMap.get(HttpServletResponse.class.getSimpleName())] = resp;

        try {
            Method method = handler.getMethod();
            Object controller = handler.getController();
            Object invoke = null;

            //如果该类、该方法 被权限注解修饰，必须判断是否拥有权限.
            //通过生成代理类，在代理类中进行逻辑判断
            if (handler.getController().getClass().isAnnotationPresent(Security.class)
                    || method.isAnnotationPresent(Security.class)) {
                SecurityProxy securityProxy = new SecurityProxy(controller);
                controller = securityProxy.getCglibProxy(username);
            }
            invoke = method.invoke(controller, parameterArr);
            String s = JSON.toJSONString(invoke);

            writer.write(s);
            writer.flush();
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            writer.close();
        }
    }

    /**
     * TODO: 根据请求uri获取处理器映射器对象handler
     *
     * @param requestUri 请求地址
     * @return com.wyj.springmvc.framework.Handler
     * @author wuyijie
     * @date 2021/11/24 3:10 下午
     */
    private Handler getHandler(String requestUri) {
        for (Handler handler : handlerMappings) {
            Matcher matcher = handler.getPattern().matcher(requestUri);
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }

    /**
     * TODO: 将首字母转换成小写
     *
     * @param str 字符串
     * @return java.lang.String
     * @author wuyijie
     * @date 2021/11/24 11:09 上午
     */
    private String lowerFirstLetter(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }


}
